Skip to content

Conversation

@giranm
Copy link

@giranm giranm commented Feb 9, 2026

Summary

Implements a comprehensive authentication system with OAuth2 PKCE flow and multi-profile support:

Authentication Features:

  • OAuth2 login with PKCE (Proof Key for Code Exchange) flow using Braintrust MCP endpoints
  • API key authentication fallback for headless/CI environments
  • Automatic token refresh for expired OAuth2 tokens
  • Profile-based credential storage in ~/.config/bt/config.json
  • Support for multiple named profiles with --profile flag (default: "DEFAULT")

Profile Management:

  • Store API URL, access token, refresh token, expiry, org_name, and default project per profile
  • bt auth login - OAuth2 or API key login
  • bt auth token - Display current token and TTL
  • bt auth logout - Remove profile

Credential Resolution:

  • Priority: --api-key flag > profile auth > SDK fallback
  • OAuth2 tokens work without org_name (JWT contains identity)
  • API keys require org_name for x-bt-org-name header
  • Project resolution: --project flag > BRAINTRUST_DEFAULT_PROJECT env > profile.project

Projects API Updates:

  • Conditional org_name handling for OAuth2 vs API key authentication
  • list_projects, create_project, get_project_by_name support both auth methods

Test Coverage:

  • 20 new tests for auth.rs covering PKCE generation, token refresh, OAuth parsing
  • 10 new tests for config.rs covering profile CRUD operations
  • All 51 unit tests passing

Recording

cli_update.mov

Implements a comprehensive authentication system with OAuth2 PKCE flow and multi-profile support:

Authentication Features:
- OAuth2 login with PKCE (Proof Key for Code Exchange) flow using Braintrust MCP endpoints
- API key authentication fallback for headless/CI environments
- Automatic token refresh for expired OAuth2 tokens
- Profile-based credential storage in ~/.config/bt/config.json
- Support for multiple named profiles with --profile flag (default: "DEFAULT")

Profile Management:
- Store API URL, access token, refresh token, expiry, org_name, and default project per profile
- bt auth login - OAuth2 or API key login
- bt auth token - Display current token and TTL
- bt auth logout - Remove profile

Credential Resolution:
- Priority: --api-key flag > profile auth > SDK fallback
- OAuth2 tokens work without org_name (JWT contains identity)
- API keys require org_name for x-bt-org-name header
- Project resolution: --project flag > BRAINTRUST_DEFAULT_PROJECT env > profile.project

Projects API Updates:
- Conditional org_name handling for OAuth2 vs API key authentication
- list_projects, create_project, get_project_by_name support both auth methods

Test Coverage:
- 20 new tests for auth.rs covering PKCE generation, token refresh, OAuth parsing
- 10 new tests for config.rs covering profile CRUD operations
- All 51 unit tests passing

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Comment on lines +23 to +24
// OAuth2 tokens don't need org_name filtering
"/v1/project".to_string()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

they do, actually. in fact, oauth tokens are not scoped to orgs, but api keys are.

in general, this code should be not any different based on what type of token you use.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for clarifying - noted!

Comment on lines +14 to +17
/// Auth profile to use
#[arg(long, env = "BRAINTRUST_PROFILE", default_value = "DEFAULT")]
pub profile: String,

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is an auth profile in the context of braintrust?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the context of Braintrust, I believe the profile should be used to target specific organisations and/or projects.

A practical example of this may include a bigger enterprise with multiple business units who may wish to segregate out per environment/use-case.

e.g. Org: BU1, Project: PROD-AGENTS

Comment on lines +29 to +45
pub struct LoginArgs {
/// Profile name to use
#[arg(long, default_value = "DEFAULT")]
pub profile: String,

/// API URL (defaults to https://api.braintrust.dev)
#[arg(long)]
pub api_url: Option<String>,

/// Use API key instead of OAuth2 (for headless/CI)
#[arg(long)]
pub api_key: bool,

/// Optional default project for this profile
#[arg(long)]
pub project: Option<String>,
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these arguments should not be duplicated from the base

return Ok(orgs[selection].clone());
}

// Fallback: prompt user for org name with helpful guidance
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these fallbacks are usually bugs

.context("failed to parse OAuth discovery response")
}

fn generate_code_verifier() -> String {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would rather use an oauth library

@giranm
Copy link
Author

giranm commented Feb 10, 2026

Discussed with @parkerhendo and @ankrgyl - a separate PR will be raised for profile switching only.
Auth work to be re-reviewed as part of larger body of work.

@giranm giranm closed this Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants